import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { In, Repository } from 'typeorm';
import { Logs } from 'src/logs/logs.entity';
import { setQueryBuilder } from 'src/untils/setQueryBuilder';
import { Roles } from 'src/roles/entities/role.entity';
import * as argon2 from 'argon2';

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User)
        private userRepository: Repository<User>,
        // 注入日志实体
        @InjectRepository(Logs)
        private logsRepository: Repository<Logs>,
        @InjectRepository(Roles)
        private rolesRepository: Repository<Roles>,
    ) { }

    /* 
      如果是注册用户 不会传入profile 、 roles 信息 做逻辑判断 
      Partial TS内置的属性 将User的属性变成可选属性
      */
    async create(createUserDto: Partial<User>) {
        // 第一次注册/新增用户 自动绑定test角色
        if (!createUserDto.roles) {
            const testRole = await this.rolesRepository.findOne({
                where: { id: 'd130b830-0549-4e71-9024-245fb577ed51' },
            });
            createUserDto.roles = [testRole];
        }
        // 新增用户时  选取了多个角色时
        if (createUserDto.roles instanceof Array) {
            createUserDto.roles = await this.rolesRepository.find({
                where: {
                    id: In(createUserDto.roles), //  此时 createUserDto.roles 是数组
                },
            });
        }
        const userTemp = this.userRepository.create(createUserDto);
        userTemp.password = await argon2.hash(userTemp.password);
        // return await this.userRepository.save(userTemp);
    }

    async findAll(query: UserQuery) {
        /* 
        联合查询
        查询条件
        page        页码
        limit       每页条数
        condition   查询条件（username、role、gender）
        sort        排序
        find方法 查询所有  也可以配置
        */
        let { limit, page = 0, username, role, gender } = query;
        let take = limit * 1 || 4;

        // 下面的两种方法都是 客户端传参时 username  gender role 都有参数 如果说只有一个参数传递呢？
        let whereObj = {
            'user.username': username,
            'profile.gender': gender,
            'roles.id': role,
        };
        const queryBuilder = this.userRepository
            .createQueryBuilder('user')
            .select(['user.id' , 'user.username' , 'user.updatedAt' , 'user.createdAt'])
            .leftJoinAndSelect('user.profile', 'profile')
            .leftJoinAndSelect('user.roles', 'roles');
            
        const newQuery = setQueryBuilder(queryBuilder, whereObj);
        return newQuery
            .offset(page * take)
            .limit(take)
            .getMany();

        // 1. 方式1
        // return await this.userRepository.find({
        //     // 设置返回数据
        //     select:['id' , 'username'],
        //     // 联表查询
        //     relations:['roles','profile'],
        //     // 查询条件
        //     where:{
        //         username,
        //         profile:{
        //             gender:newgender
        //         },
        //         roles:{
        //             id:role
        //         }
        //     },
        //     // 设置分页条件
        //     take,
        //     skip:(page) * take,
        //     // 排序
        //     order:{
        //         id:'DESC'
        //     }
        // });

        // 方式2 用queryBuilder 改装
        // return (
        //     this.userRepository
        //         .createQueryBuilder('user')
        //         .leftJoinAndSelect('user.profile' , 'profile')
        //         .leftJoinAndSelect('user.roles' , 'roles')
        //         .where('user.username = :username' , {username}) // :后面的值 和 变量一样就
        //         .andWhere('profile.gender = :gender' , {gender})
        //         .andWhere('roles.id = :role' , {role})
        //         .offset((page) * take)
        //         .limit(take)
        //         .getMany()
        // )
    }

    async findOne(id: string) {
        return await this.userRepository.findOne({
            where: { id },
            relations: ['roles', 'profile', 'roles.menus'],
        });
    }

    async findByUsername(username: string) {
        return await this.userRepository.findOne({
            where: { username },
            relations: ['roles', 'roles.menus'],
        });
    }

    // 第一个参数是 userId  第二个参数是要修改的对象
    async update(id: string, updateUserDto: User) {
        // 如果 传参是 那么就需要用到多模型联合更新   entity.ts 中的OnetoOne结构中 设置cascade为true
        //   username: 'lrq~',
        //   password: 123456,
        //   profile: { gender: 2, address: '杭州' }

        const userTemp = await this.findOne(id);

        // 如果传递了roles  也可以更新 用户的角色表
        if (updateUserDto.roles && Array.isArray(updateUserDto.roles)) {
            updateUserDto.roles = await this.rolesRepository.find({
                where: { id: In(updateUserDto.roles) },
            });
        }

        const user = this.userRepository.merge(userTemp, updateUserDto); // merge方法 将多个实体合并为一个实体

        return await this.userRepository.save(user); // 调用save方法 完成更新多模型

        // 更新用户  只适用于单模型更新
        // return await this.userRepository.update(id, updateUserDto);
    }

    async remove(id: string) {
        // delete 和 remove方法的不同
        // remove删除可以 使用 typeorm的钩子函数监听  传入的参数是实例 并非id  delete删除 传入的参数是id实例
        return await this.userRepository.delete(id);
    }

    async updateToken(userid: string, token: string) {
        return await this.userRepository.update({ id: userid }, { token });
    }

    // 以下是联表查询
    // 一对一联表  user - profile
    async findProfile(id: string) {
        // 控制是否展示profile
        return await this.userRepository.findOne({
            where: { id },
            relations: { profile: true },
        });
    }

    // 一对多联表查询  user - logs
    async findLogs(id: string) {
        // userRepository.findOne 方法查找指定用户，并通过 relations 选项关联 logs 表，然后获取关联的日志数据
        return await this.userRepository.findOne({
            where: { id },
            relations: ['logs'],
        });
    }

    // QueryBuilder 查询
    // 查询日志 并分组（以练习为主）  可以支持原生sql  看最下面
    async findLogsByGroup(id: string) {
        /* 
            SELECT
                l.code , COUNT(l.`code`) count
            FROM `logs` l , `user` u WHERE u.id = l.userId AND u.id = '083293c7-47f6-4f61-9fa6-af671cab7a8c' GROUP BY l.`code` ;
            */

        return this.logsRepository
            .createQueryBuilder('logs') // 查询哪张表
            .select('logs.code', 'code') // 查询的属性  第二个参数 别名
            .addSelect('Count("logs.code")', 'count') // 查询的属性  第二个参数 别名
            .leftJoinAndSelect('logs.user', 'user') // 左连接
            .where('user.id = :id', { id }) // 查询条件
            .groupBy('logs.code') // 分组
            .orderBy('code', 'ASC') //  ASC 正序  DESC 倒序
            .limit(3) // 页码
            .offset(0) // 偏移量
            .getRawMany();

        /* 
            SELECT
                l.`code` , l.id , l.path , l.methods,
                JSON_OBJECT('id' , u.id , 'username' , u.username ) user 
            FROM `logs` l , `user` u WHERE u.id = l.userId AND u.id = '083293c7-47f6-4f61-9fa6-af671cab7a8c'; 
            将这条sql语句转换成QueryBuilder查询
            */

        // return await this.logsRepository
        //     .createQueryBuilder('logs')
        //     .leftJoinAndSelect('logs.user', 'user')  // 左连接
        //     .select(['logs.code', 'logs.id', 'logs.path', 'logs.methods', 'JSON_OBJECT(\'id\', user.id, \'username\', user.username) AS user'])
        //     .where('user.id = :id', { id })  // 查询条件
        //     .getRawMany();

        /* 原生sql写法 不建议 
            return this.logsRepository.query(` 
            SELECT
                l.code , COUNT(l.code) count
            FROM logs l , user u WHERE u.id = l.userId AND u.id = '${id}' GROUP BY l.code ; 
            `)
            */
    }
}
